/*
 * Fichier: ExercisePane.java
 * Crée le: 15 septembre 2007.
 * Modifié: 20 juin 2012.
 * Auteurs: Sébastien ESTIENNE.
 * SiteWeb: http://www.prog-info.org/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.OF
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package javaexos.gui;



import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javaexos.controller.ExerciseController;
import javaexos.gui.common.GConsole;
import javaexos.model.ExerciseModel;
import javaexos.tools.Constants;
import javaexos.tools.ExerciseLauncher;
import javaexos.tools.HTMLConstants;
import javaexos.tools.JavaHighlighter;
import javaexos.tools.Log;
import javaexos.tools.XMLArticleParser;
import javaexos.view.ExerciseView;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;



/**
 * <p>Panneau d'informations d'un exercice.</p>
 * @author Sébastien ESTIENNE.
 */
public class ExercisePane extends JPanel implements ExerciseView
{
   /**
    * <p>Serial version UID.</p>
    */
   private static final long serialVersionUID = 1L;

   /** Logger. */
   private static final Logger LOGGER = Log.getInstance().getLogger("javaexos.gui.SourceLoader");
   
   /** Les informations de l'exercice courant. */
   private ExerciseModel exercise;

   /** Le contrôleur. */
   @SuppressWarnings("unused")
   private ExerciseController controller;

   /** Panneau d'onglets d'un exercice. */
   private JTabbedPane exerciseTabs;

   /** Panneau contenant les informations d'un exercice. */
   private JPanel declarationPanel;

   /** Texte de l'énnoncé. */
   private JTextPane declarationText;

   /** Panneau contenant le bouton de démonstration. */
   private JPanel demoPanel;

   /** Bouton permettant de lancer l'exercice. */
   private JButton demoButton;

   /** L'icône pour le bouton de lancement. */
   private static ImageIcon launchIcon;

   /** Onglets des fichiers sources. */
   private JTabbedPane sourceTabs;

   /** Editeur de Kit HTML. */
   private HTMLEditorKit htmlKit;

   /** Document HTML. */
   private HTMLDocument htmlDoc;

   /** Feuille de style. */
   private StyleSheet styles;


   /**
    * <p>Construit le panneau pour un exercice.</p>
    * @param exercise Les informations de l'exercice.
    * @param controller Le contrôleur associé.
    */
   public ExercisePane(ExerciseModel exercise, ExerciseController controller)
   {
      super();

      // Initialisation.
      this.exercise = exercise;
      this.controller = controller;

      initComponents();
   }


   /**
    * <p>Initialise le panneau pour l'affichage des exercices.</p>
    */
   private void initComponents()
   {
      setLayout(new BorderLayout(5, 5));

      // Initialise la feuille de style.
      this.styles = new StyleSheet();
      this.styles.importStyleSheet(getClass().getResource(Constants.JAVAEXOS_STYLESHEET));
      this.htmlKit = new HTMLEditorKit();
      this.htmlKit.setStyleSheet(this.styles);

      // Initialise le document HTML.
      this.htmlDoc = (HTMLDocument) this.htmlKit.createDefaultDocument();

      // Initialise les onglets.
      this.exerciseTabs = new JTabbedPane();
      this.exerciseTabs.removeAll();

      // Initialise le composant contenant le texte de l'énnoncé.
      this.declarationText = new JTextPane();
      this.declarationText.setEditable(false);
      this.declarationText.setFocusable(false);
      this.declarationText.setEditorKit(this.htmlKit);
      this.declarationText.setDocument(this.htmlDoc);
      this.declarationText.setContentType("text/html");

      // Initialise le panneau de démonstration.
      this.demoPanel = new JPanel();
      this.demoPanel.setBackground(Color.WHITE);
      this.demoPanel.setLayout(new FlowLayout());

      // Initialise le bouton de démonstration.
      this.demoButton = new JButton("Lancer la démo");
      launchIcon = new ImageIcon(getClass().getResource(Constants.LAUNCH_ICON_PATH));
      this.demoButton.setIcon(launchIcon);
      this.demoButton.setMnemonic('d');
      this.demoButton.addActionListener(new ActionListener()
      {
         /**
          * Invoqué quand l'utilisateur click sur le bouton de démo.
          * @param e L'événement relatif à l'action effectuée.
          */
         public void actionPerformed(ActionEvent e)
         {
            // Création d'un thread pour le lancement des démos (évite le freeze de l'IHM).
            Thread t = new Thread()
            {
               /**
                * Méthode appelée lors du démarrage du thread.
                */
               @Override
               public void run()
               {
                  // Lancement de la démo de l'exercice.
                  ExerciseLauncher launcher = new ExerciseLauncher(
                        ExercisePane.this.exercise);
                  boolean result = launcher.execute();

                  // Affichage d'une pseudo console lorsque c'est nécessaire.
                  if(result && launcher.getOutput() != null && !launcher.getOutput().isEmpty())
                  {
                     GConsole console = GConsole.getInstance();
                     console.clear();
                     console.print(launcher.getOutput());
                     console.setVisible(true);
                  }
               }
            };
            t.start();
         }

      });
      this.demoPanel.add(this.demoButton);

      // Initialise le panneau de l'énoncé.
      this.declarationPanel = new JPanel();
      this.declarationPanel.setLayout(new BorderLayout(5, 5));
      this.declarationPanel.add(new JScrollPane(this.declarationText), BorderLayout.CENTER);
      this.declarationPanel.add(this.demoPanel, BorderLayout.SOUTH);
      this.exerciseTabs.addTab("Enoncé", this.declarationPanel);

      // Initialise le panneau contenant le code source.
      this.sourceTabs = new JTabbedPane();
      JPanel panelSource = new JPanel();
      panelSource.setLayout(new BorderLayout());
      panelSource.add(this.sourceTabs, BorderLayout.CENTER);
      this.exerciseTabs.addTab("Solution", panelSource);

      // Ajout des onglets d'exercices au panneau d'exercice.
      add(this.exerciseTabs, BorderLayout.CENTER);
   }


   /**
    * <p>Change l'exercice courant.</p>
    * @param exercise Le nouvel exercice.
    */
   public void setExercise(ExerciseModel exercise)
   {
      this.exercise = exercise;

      updatePane();
   }
  

   /**
    * <p>Met à jour le panneau d'exercices.</p>
    */
   private void updatePane()
   {
      // Re-initialisation.
      this.declarationText.setText("");
      this.sourceTabs.removeAll();
      this.exerciseTabs.setSelectedIndex(0);

      // Enoncé.
      StringBuilder declaration = new StringBuilder();
      declaration.append(HTMLConstants.HTML_OPEN_MARKER);
      declaration.append(HTMLConstants.BODY_OPEN_MARKER);
      declaration.append(HTMLConstants.H1_OPEN_MARKER);
      declaration.append(this.exercise.getTitle());
      declaration.append(HTMLConstants.H1_CLOSED_MARKER);
      declaration.append(XMLArticleParser.parse(this.exercise.getDeclaration()));
      declaration.append(HTMLConstants.BODY_CLOSED_MARKER);
      declaration.append(HTMLConstants.HTML_CLOSED_MARKER);
      this.declarationText.setText(declaration.toString());
      this.declarationText.setCaretPosition(0);

      // Variable pour la partie code source.
      JTextPane sourcePane;
      JScrollPane sourceScrollPane;
      Font sourceFont = new Font("Monospaced", Font.PLAIN, 12);
      HTMLDocument sourceHtmlDoc;
      
      // Code source des différentes classes de l'exercice.
      for(String filename : this.exercise.getFiles())
      {
         sourceHtmlDoc = (HTMLDocument) this.htmlKit.createDefaultDocument();
         sourcePane = new JTextPane();
         sourcePane.setEditable(false);
         sourcePane.setEditorKit(this.htmlKit);
         sourcePane.setDocument(sourceHtmlDoc);
         sourcePane.setContentType("text/html");
         sourcePane.setFont(sourceFont);
         sourcePane.setText("Lecture du fichier en cours...");
         sourcePane.setCaretPosition(0);
         sourceScrollPane = new JScrollPane(sourcePane);
         String[] splitFilename = filename.split("/");
         this.sourceTabs.addTab(splitFilename[splitFilename.length - 1], sourceScrollPane);
      }
      
      // Ajoute un événement permettant de lire le code source des fichiers d'un exercice.
      SourceLoader loader = new SourceLoader();
      this.sourceTabs.addChangeListener(loader);
      
      // Provoque la lecture du code source du premier fichier de l'exercice.
      loader.stateChanged(new ChangeEvent(this.sourceTabs));
   }

   /**
    * <p>Lecture de fichier de code source des exercices.</p>
    */
   private class SourceLoader implements ChangeListener
   {
      /** Liste des index des fichiers déjà chargés. */
      private List<Integer> loadedFiles;
      
      /** Source de l'événement. */
      private JTabbedPane source;
      
      /**
       * <p>Constructeur.</p>
       */
      public SourceLoader()
      {
         this.loadedFiles = new ArrayList<Integer>();
      }
      
      /**
       * <p>Invoqué quand un fichier de code source doit être affiché.</p>
       * @param e Un événement indiquant le changement d'état.
       */
      public void stateChanged(ChangeEvent e)
      {
         // Récupère le composant source.
         SourceLoader.this.source = (JTabbedPane) e.getSource();
         
         // Tâche permettant d'afficher le code source coloré.
         Runnable task = new Runnable()
         {
            public void run()
            {
               // Récupération de l'index du fichier de code source.
               int index = SourceLoader.this.source.getSelectedIndex();
               
               // Récupération de la liste des fichiers sources.
               List<String> files = ExercisePane.this.exercise.getFiles();
               
               // Charge le fichier si cela n'a pas encore été fait.
               if(index != -1 && index < files.size() 
                     && !SourceLoader.this.loadedFiles.contains(index))
               {
                  try
                  {
                     // Nom complet du fichier de code source.
                     String filename = "/" 
                        + Constants.CHAPTERS_NAME.replace("?", Integer.toString(
                              ExercisePane.this.exercise.getChapter().getNumber())) 
                        + "/" + files.get(index);
                     
                     // Récupération du composant affichant le code source.
                     JScrollPane scroll = (JScrollPane) SourceLoader.this.source.getSelectedComponent();
                     JTextPane text = (JTextPane) ((JViewport) scroll.getComponent(0)).getComponent(0);
                     
                     // Coloration syntaxique du code source et affichage.
                     URL url = getClass().getResource(filename);
                     String sourceCode = JavaHighlighter.getInstance().parse(url);
                     text.setText(sourceCode);
                     text.setCaretPosition(0);
                     
                     // Ajoute le fichier à la liste des fichiers chargés.
                     SourceLoader.this.loadedFiles.add(index);
                  }
                  catch(Exception ex)
                  {
                     StringBuilder message = new StringBuilder();
                     message.append(files.get(index));
                     message.append(" : Impossible d'afficher le code source du fichier. ");
                     message.append(ex.toString());
                     LOGGER.log(Level.WARNING, message.toString());
                     
                     JOptionPane.showMessageDialog(ExercisePane.this, 
                           "Impossible d'afficher le code source du fichier " + files.get(index), 
                           "Source Loader", JOptionPane.ERROR_MESSAGE);
                  }
               }
            }
         };
         
         // lance la tâche.
         SwingUtilities.invokeLater(task);
      }
   }
}
